Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Measure writing guide: add section about I/O file + modernize #25

Merged
merged 3 commits into from
Jun 21, 2021

Conversation

jmarrec
Copy link
Contributor

@jmarrec jmarrec commented Jun 3, 2021

Measure writing guide: add section about I/O file + modernize

Fixes NREL/OpenStudio#4046

Fixes NREL/OpenStudio#3609

Comment on lines +58 to +64
Please note the original namespace in versions of OpenStudio < 2.X `OpenStudio::Ruleset` is deprecated and replaced with `OpenStudio::Measure`.
Measure classes prior to 2.X are also deprecated:

| < 2.X (Deprecated) | >= 2.X |
|------------------------------------------|-----------------------------------|
| OpenStudio::Ruleset::ModelUserScript | OpenStudio::Measure::ModelMeasure |
| OpenStudio::Ruleset::WorkspaceUserScript | OpenStudio::Measure::ModelMeasure |
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did some changed re OpenStudio::Ruleset > OpenStudio::Measure

Comment on lines +129 to +130
args = OpenStudio::Measure::OSArgumentVector.new
insl_thckn = OpenStudio::Measure::makeDoubleArgument('insl_thckn',true)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One of many

Comment on lines +1476 to +1481
### Reporting Measure arguments Method

```ruby
def arguments(model = nil)
```

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added

Comment on lines +1569 to +1600
### Outputting a HTML (or other) file

You can create any file in the current working directory named `report*.*` and it will be copied over to the `reports/` directory via the openstudio-workflow gem.
The name of the resulting file is computed from the measure class name and the filename.

If your measure class name is `ReportingMeasureName`:

```ruby

class ReportingMeasureName < OpenStudio::Measure::ReportingMeasure

[...]

def run(runner, user_arguments)
super(runner, user_arguments)

# Outputs to: reports/reporting_measure_name_report_one.html
File.open('./report_one.html', 'w') do |file|
# Write file
end

# Outputs to: reports/reporting_measure_name_qa_qc.csv
File.open('./qa_qc.csv', 'w') do |file|
# Write file
end
end
end

FooBar.new.registerWithApplication
```


Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Document the classic case: output a report file from a ReportingMeasure.

Comment on lines +1744 to +1798
## Using files and using ExternalFile in a measure

It is sometimes needed to create support files in the process of running the measure. This section describes how path handling works in the context of a measure.

When running a measure, the current working directory is something like './run/000_measure_class_name/' (this is the output that `File.realpath('./')` will give you).
None of the files created in this directory will actually be copied over, unless it is the special case described in the section 'Reporting Measures' > 'Outputting an HTML (or other file)'.

### Measure resource files

#### Where to place them
Resource files for a measure should be placed in the `resources/` subfolder like the Measure File Structure section indicates to do with additional ruby code.
Note that nested levels are not accepted, meaning that given the below tree, anything in `resources/subfolder` is not valid: `anotherschedulefile.csv` will not be copied over with the measure.

```
├── measure.rb
├── measure.xml
├── resources
│   ├── schedulefile.csv
│   ├── subfolder
│   │   ├── anotherschedulefile.csv
```

#### How to access them inside a measure

You can locate your measure resources by using a relative path to the `measure.rb` you are running by using `File.join(File.dirname(__FILE__), 'schedulefile.csv')`

#### How to use an ExternalFile inside a measure

The constructor for `ExternalFile` will automatically copy the file at the path you provide to the first element in `WorkflowJSON::filePaths[0]`. When running a measure, the openstudio-workflow gem prepends the `generated_files` subdirectory.

```ruby
class CreateScheduleFile < OpenStudio::Measure::ModelMeasure

[...]

def run(model, runner, user_arguments)

# Locate the resource file (this resolves to something like './measures/resources/schedulefile.csv')
csv_in_path = File.join(File.dirname(__FILE__), 'schedulefile.csv')

# Instantiate an External File: this will automatically copy to first path in WorkflowJSON: `runner.workflow.filePaths[0]`, typically './generated_files/'
externalFile_ = OpenStudio::Model::ExternalFile::getExternalFile(model, csv_in_path)
if (!externalFile_)
runner.registerError("Failed to instantiate an External File")
return false
end

column = 1
rowsToSkip = 1
scheduleFile = OpenStudio::Model::ScheduleFile.new(externalFile_.get(), column, rowsToSkip)
scheduleFile.setName("ExampleScheduleFile")

return true
end
```
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This section is fair game and general enough. I definitely think it should be added.

Comment on lines +1800 to +1806
#### How to output any other file

To output any file you may need that isn't an `ExternalFile`, you should rely on two things, in order of preferences:
* `runner.workflow.filePaths[0]`: this will typically resolve to `./generated_files`
* `runner.workflow.absoluteRootDir`: this will resolve to '.'

`.` is the location defined as the `root` key inside the `workflow.osw`, or if not specified the location of the `workflow.osw` itself.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find that relevant, but it's possible other people might not?

Comment on lines +1815 to +1822
# Locate the resource file (this resolves to something like './measures/resources/schedulefile.csv')
csv_in_path = File.join(File.dirname(__FILE__), 'schedulefile.csv')

# Canonical way: write to the ./generated_files directory
out_file = File.join(runner.workflow.filePaths[0].to_s, 'myfile.csv')
File.open(out_file, 'w') do |f|
f << "Hello"
end
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that part is definitely fine.

Comment on lines +1824 to +1846
# Prefer the above, but one valid use case would be to output to the reports/ folder
# (this is a *ModelMeasure*, you cannot just name it './report.html'
# and have it copied automatically like described above in Reporting measure section)

# either /tmp/osmodel-1622719126-1/ApplyMeasureNow
# or ./<model_companion_dir>/
rootDir = runner.workflow.absoluteRootDir.to_s

html_out_path = 'report.html'
if (File.basename(rootDir) == 'ApplyMeasureNow')
html_out_path = File.absolute_path(
File.join(rootDir, '..', 'resources', 'reports', html_out_path))
else
html_out_path = File.absolute_path(
File.join(rootDir, 'reports', html_out_path))
end
outDir = File.dirname(html_out_path)
if !File.exists?(outDir)
FileUtils.mkdir_p(outDir)
end
File.open(html_out_path, 'w') do |f|
f << "<html><head><title>My Custom Report that works with Apply Now!</title></head><body><h1>Hello World!</h1></body></html>"
end
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So perhaps this section is overkill or an invitation for bad ideas. I don't know. But people are definitely already doing stuff like this (cf discussion on NREL/OpenStudio#4046) except they are not doing it right for lack of documentation.

Comment on lines +1857 to +1869
Regarding Note A in the above code, note that this is added to the `WorkflowStepResult` `step_files` entry (WorkflowJSON: root > "steps" [] > "result" > "step_files", see [Workflow JSON schema](https://github.com/NREL/OpenStudio-workflow-gem/blob/e569f910be364d33c3ddb1a655570c85f1b24bfa/spec/schema/osw_output.json#L251))

It possible to capture the path to the stepFiles from a previous step inside a measure like the following:

```ruby
if runner.workflow.currentStepIndex() > 0
previousStep = runner.workflow.workflowSteps[runner.workflow.currentStepIndex() - 1]
if previousStep.result
previousStepResult = previousStep.result.get
runner.registerWarning("previousStepResult.stepFiles=#{previousStepResult.stepFiles.map{|p| p.to_s}}")
end
end
```
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have a good use case for this but perhaps @DavidGoldwasser might. I won't be offended if you don't lik that section

@tijcolem
Copy link
Collaborator

@jmarrec Changes look good. Thanks for the updates. I just assigned this to myself. I'd like to build/convert the docs to html before merging to gh-pages branch.

@tijcolem
Copy link
Collaborator

Looks good served up locally using mkdocs.

@tijcolem tijcolem merged commit 14154eb into NREL:master Jun 21, 2021
tijcolem added a commit that referenced this pull request Jun 21, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
2 participants